/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cm_trace.cpp - ...
//


#include "cm_local.h"


struct traceWork_t {
	bool		isTest;
	bool		isPoint;

	int			contentMask;

	glqVec3		start;
	glqVec3		end;

	glqBounds	size;
	glqBounds	bounds;

	float		radius;

	glqVec3		boxExtents;
	glqPlane	boxPlanes[6];
	glqVec3		boxOffsets[8];

	glqPlane	heartPlanes[2];

	trace_t *	trace;
};


/*
 ==============================================================================

 POSITION TESTING

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void CM_TestPointInWorld (traceWork_t *tw){

}

/*
 ==================
 
 ==================
*/
static void CM_TestBoxInWorld (traceWork_t *tw, int num){

}

/*
 ==================
 
 ==================
*/
static void CM_TestInInlineModel (traceWork_t *tw, clipInlineModel_t *model){

}


/*
 ==============================================================================

 TRACING

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void CM_TraceThroughLeaf (traceWork_t *tw, clipLeaf_t *leaf){

}

/*
 ==================
 
 Traverse all the contacted leaves from the start to the end position.
 If the trace is a point, they will be exactly in order, but for larger trace
 volumes it is posible to hit something in a later leaf with a smaller
 intercept fraction.
 ==================
*/
static void CM_TraceThroughWorld (traceWork_t *tw, int num, const glqVec3 &p1, const glqVec3 &p2, float pf1, float pf2){

	clipClipNode_t	*node;
	clipLeaf_t		*leaf;
	glqPlane		*plane;

	if (tw->trace->fraction <= pf1)
		return;		// Already hit something nearer

	// If < 0, we are in a leaf node
	if (num < 0){
		leaf = &cm.leafs[-1 - num];

		if (!leaf->bounds.IntersectsBounds(tw->bounds))
			return;			// Doesn't intersect the bounds

		cm_stats.leafTraces++;

		// Trace through the leaf
		CM_TraceThroughLeaf(tw, leaf);
		return;
	}

	// FIXME: this ues clipNodes, not regular nodes...
	node = &cm.clipNodes[num];
}

/*
 ==================
 
 NOTE: do this like Q2e? (if this is possible with Quake...)
 ==================
*/
static void CM_TraceThroughInlineModel (traceWork_t *tw, clipInlineModel_t *model){

}

/*
 ==================
 CM_Trace

 TODO: add traces for alias models too?
 ==================
*/
void CM_Trace (const glqVec3 &start, const glqVec3 &end, const glqBounds &bounds, int contentMask, clipModel_t *model, const glqVec3 &modelOrigin, const glqMat3 &modelAxis, trace_t *trace){

	traceWork_t	tw;
	glqVec3		offset, normal, dir;
	glqVec3		rVector, uVector;
	int			i;

	if (!cm.loaded)
		Com_Error(false, "CM_Trace: map not loaded");

	cm.checkCount++;		// For multi-check avoidance

	// Adjust so that the bounds are always symmetric, which avoids some
	// complications with plane expanding of rotated models
	offset = bounds.GetCenter();

	// Transform start and end into local space
	if (model == NULL){
		tw.start = start + offset;
		tw.end = end + offset;
	}
	else {
		modelAxis.ProjectVector((start + offset) - modelOrigin, tw.start);
		modelAxis.ProjectVector((end + offset) - modelOrigin, tw.end);
	}

	// Set size
	tw.size = bounds - offset;

	// Set content mask
	tw.contentMask = contentMask;

	// Check for position test special case
	if (start == end)
		tw.isTest = true;
	else {
		tw.isTest = false;

		// Set up trace radius
		tw.radius = tw.size.GetRadius() + BOX_EPSILON;

		// Set up trace heart planes
		dir = tw.end - tw.start;
		dir.Normalize();
		dir.NormalVectors(rVector, uVector);

		tw.heartPlanes[0].Set(rVector, tw.start.Dot(rVector));
		tw.heartPlanes[1].Set(uVector, tw.start.Dot(uVector));
	}

	// Check for point special case
	if (bounds.IsZeroed()){
		tw.isPoint = true;

		// Calculate bounds
		if (tw.isTest){
			for (i = 0; i < 3; i++){
				tw.bounds[0][i] = tw.start[i];
				tw.bounds[1][i] = tw.start[i];
			}
		}
		else {
			for (i = 0; i < 3; i++){
				if (tw.start[i] < tw.end[i]){
					tw.bounds[0][i] = tw.start[i] - BOX_EPSILON;
					tw.bounds[1][i] = tw.end[i] + BOX_EPSILON;
				}
				else {
					tw.bounds[0][i] = tw.end[i] - BOX_EPSILON;
					tw.bounds[1][i] = tw.start[i] + BOX_EPSILON;
				}
			}
		}
	}
	else {
		tw.isPoint = false;

		// Calculate bounds
		if (tw.isTest){
			for (i = 0; i < 3; i++){
				tw.bounds[0][i] = tw.start[i] + tw.size[0][i];
				tw.bounds[1][i] = tw.start[i] + tw.size[1][i];
			}
		}
		else {
			for (i = 0; i < 3; i++){
				if (tw.start[i] < tw.end[i]){
					tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - BOX_EPSILON;
					tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + BOX_EPSILON;
				}
				else {
					tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - BOX_EPSILON;
					tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + BOX_EPSILON;
				}
			}
		}

		// Set box extents
		tw.boxExtents = tw.size[1];

		// Set box planes
		tw.boxPlanes[0].Set( 1.0f,  0.0f,  0.0f, -tw.bounds[1][0]);
		tw.boxPlanes[1].Set(-1.0f,  0.0f,  0.0f,  tw.bounds[0][0]);
		tw.boxPlanes[2].Set( 0.0f,  1.0f,  0.0f, -tw.bounds[1][1]);
		tw.boxPlanes[3].Set( 0.0f, -1.0f,  0.0f,  tw.bounds[0][1]);
		tw.boxPlanes[4].Set( 0.0f,  0.0f,  1.0f, -tw.bounds[1][2]);
		tw.boxPlanes[5].Set( 0.0f,  0.0f, -1.0f,  tw.bounds[0][2]);

		// Set box offsets to appropriate corners from origin
		tw.boxOffsets[0].Set(tw.size[0][0], tw.size[0][1], tw.size[0][2]);
		tw.boxOffsets[1].Set(tw.size[1][0], tw.size[0][1], tw.size[0][2]);
		tw.boxOffsets[2].Set(tw.size[0][0], tw.size[1][1], tw.size[0][2]);
		tw.boxOffsets[3].Set(tw.size[1][0], tw.size[1][1], tw.size[0][2]);
		tw.boxOffsets[4].Set(tw.size[0][0], tw.size[0][1], tw.size[1][2]);
		tw.boxOffsets[5].Set(tw.size[1][0], tw.size[0][1], tw.size[1][2]);
		tw.boxOffsets[6].Set(tw.size[0][0], tw.size[1][1], tw.size[1][2]);
		tw.boxOffsets[7].Set(tw.size[1][0], tw.size[1][1], tw.size[1][2]);
	}

	// Fill in the default trace
	Mem_Fill(trace, 0, sizeof(trace_t));

	tw.trace = trace;
	tw.trace->fraction = 1.0f;
	tw.trace->direction = end - start;
	tw.trace->direction.Normalize();
	tw.trace->point = end;

	// If a position test
	if (tw.isTest){
		cm_stats.tests++;

		// Test position against the world or model
		if (model == NULL){
			if (tw.isPoint)
				CM_TestPointInWorld(&tw);
			else
				CM_TestBoxInWorld(&tw, 0);
		}
		else {
			if (cm_skipModels->integerValue)
				return;

			switch (model->type){
			case CLIP_MODEL_INLINE:
				CM_TestInInlineModel(&tw, (clipInlineModel_t *)model->data);
				break;
			default:
				Com_Error(false, "CM_Trace: bad model type (%i)", model->type);
			}
		}

		return;
	}

	cm_stats.traces++;

	// Sweep through the world or model
	if (model == NULL)
		CM_TraceThroughWorld(&tw, 0, tw.start, tw.end, 0.0f, 1.0f);
	else {
		if (cm_skipModels->integerValue)
			return;

		switch (model->type){
		case CLIP_MODEL_INLINE:
			CM_TraceThroughInlineModel(&tw, (clipInlineModel_t *)model->data);
			break;
		default:
			Com_Error(false, "CM_Trace: bad model type (%i)", model->type);
		}
	}

	// If there was a collision, transform the plane into world space
	if (model != NULL && !tw.trace->allInside && tw.trace->fraction < 1.0f){
		normal = modelAxis * tw.trace->c.plane.Normal();

		tw.trace->c.plane.SetNormal(normal);
		tw.trace->c.plane.SetDist(tw.trace->c.plane.Dist() + modelOrigin.Dot(normal));
		tw.trace->c.plane.SetSignBits();
	}

	// Calculate final position from the original, unmodified start and end
	// positions
	tw.trace->point = start + (end - start) * tw.trace->fraction;
}